package au.com.handsunited.dao.hibernate;


import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import au.com.handsunited.dao.GenericDAO;
import au.com.handsunited.jodawrapper.JodaDateTimeWrapper;
import au.com.handsunited.model.MotherOfAllEntities;

/**
 * @author Arun Chandrasekhar
 *
 */
public abstract class GenericDAOHibernate<T, ID extends Serializable> extends HibernateDaoSupport implements GenericDAO<T, ID> {

	private final Class<T> persistentClass;


	private static Logger logger = LoggerFactory.getLogger(GenericDAOHibernate.class);


	@SuppressWarnings("unchecked")
	public GenericDAOHibernate() {
		persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 *
	 * @return
	 */
	public Class<T> getPersistentClass() {
		return persistentClass;
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#create(java.lang.Object)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public T create(T newInstance) {
		return (T) getSession().merge(newInstance);
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#delete(java.lang.Object)
	 */
	@Override
	public void delete(T persistentObject) {
		getSession().delete(persistentObject);
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#findByExample(java.lang.Object)
	 */
	@Override
	public List<T> findByExample(T exampleInstance) {
		return findByExample(exampleInstance, null);
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#read(java.io.Serializable)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public T read(ID id) {
		return (T) getSession().get(persistentClass, id);
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#update(java.lang.Object)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public T update(T transientObject) {
		((MotherOfAllEntities)transientObject).setLastModified(new JodaDateTimeWrapper());
		return (T) getSession().merge(transientObject);
	}

	/**
	 * @see au.com.handsunited.dao.GenericDAO#findById(java.io.Serializable, boolean)
	 */
	@SuppressWarnings("unchecked")
	public T findById(ID id, boolean lock) {
		T entity;
		if (lock) {
			entity = (T) getSession().load(getPersistentClass(), id, LockMode.UPGRADE);
		} else {
			entity = (T) getSession().load(getPersistentClass(), id);
		}

		return entity;
	}

	/**
	 *
	 * @see au.com.handsunited.dao.GenericDAO#findAll()
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		return findByCriteria();
	}

	/**
	 *
	 * @param exampleInstance
	 * @param excludeProperty
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByExample(T exampleInstance, String[] excludeProperty) {
		Criteria crit = getSession().createCriteria(getPersistentClass());
		Example example =  Example.create(exampleInstance);

		if (excludeProperty != null) {
			for (String exclude : excludeProperty) {
				example.excludeProperty(exclude);
			}
		}

		crit.add(example);
		return crit.list();
	}


	/**
	* Use this inside subclasses as a convenience method.
	*/
	@SuppressWarnings("unchecked")
	protected List<T> findByCriteria(Criterion... criterion) {
		Criteria crit = getSession().createCriteria(getPersistentClass());
		for (Criterion c : criterion) {
		    crit.add(c);
		}
		return crit.list();
	}

}